using System;
using System.IO;

using MessageBuffer;

namespace Calibur
{
	/// <summary>
	/// Message type.
	/// </summary>
	public enum MessageType
	{
		App = 0,

		RequestSession = 1,
		SessionEstablished = 2,

		ServerHeartbeat = 3,
		ServerHeartbeatAck = 4,

		ClientHeartbeat = 5,
		ClientHeartbeatAck = 6
	}

	abstract public class Message
	{
		private static readonly DateTime Epoch = 
            new DateTime(1970, 1, 1, 0, 0, 0, 0, 
                         new System.Globalization.GregorianCalendar(), 
                         System.DateTimeKind.Utc);

		/// <summary>
		/// Get Java's System.currentTimeMillis(), which is the number 
		/// of milliseconds from midnight 1 January 1970 (UTC) .
		/// While C# DateTime.Now.Ticks starts recording from midnight 1 January 0001.
		/// </summary>
		/// <returns>
		/// Java's System.currentTimeMillis() value.
		/// </returns>
		public static UInt64 CurrentTimeMillis()
		{
			return (UInt64)((DateTime.UtcNow.Ticks - Epoch.Ticks) / TimeSpan.TicksPerMillisecond);
		}
        
		public const byte HEAD_TYPE = 1;

		public static Message CreateMessage(int messageType, MessageBeanFactory factory)
		{
			switch (messageType) {
				case (int) MessageType.App:
					return new CommandMessage(factory);
                
				case (int) MessageType.SessionEstablished:
					return new SessionEstablishedMessage();

				case (int) MessageType.ClientHeartbeatAck:
					return new HeartbeatMessage(MessageType.ClientHeartbeatAck);

				case (int) MessageType.ServerHeartbeat:
					return new HeartbeatMessage(MessageType.ServerHeartbeat);

				default:
					throw new ArgumentOutOfRangeException(
						"Incorrect inbound message type:" + messageType);
			}
		}

		private MessageType type;

		public MessageType Type {
			get { return type; }
		}

		public Message(MessageType mt)
		{
			this.type = mt;
		}

		abstract public void Serialize(Stream stream);

		abstract public void Deserialize(Stream stream);
	}

	public class HeartbeatMessage : Message
	{
		public HeartbeatMessage(MessageType mt) : base(mt)
		{
		}

		public override void Serialize(Stream stream)
		{
		}

		public override void Deserialize(Stream stream)
		{
		}
	}

	public class RequestSessionMessage : Message
	{
		private UInt64 timestamp;

		public RequestSessionMessage(UInt64 ts) : base(MessageType.RequestSession)
		{
			this.timestamp = ts;
		}

		public override void Serialize(Stream stream)
		{
			BinaryMessageBeanFormat.PutInt64(stream, (Int64)timestamp);
		}
        
		public override void Deserialize(Stream stream)
		{
			timestamp = (UInt64)BinaryMessageBeanFormat.GetInt64(stream);
		}
	}

	public class SessionEstablishedMessage : Message
	{
		private UInt64 establishedTimestamp;

		public UInt64 EstablishedTimestamp {
			get { return establishedTimestamp; }
			set { establishedTimestamp = value; }
		}

		private string sessionKey;

		public string SessionKey {
			get { return sessionKey; }
		}

		public SessionEstablishedMessage() : base(MessageType.SessionEstablished)
		{
			establishedTimestamp = 0L;
		}

		public override void Serialize(Stream stream)
		{
		}
        
		public override void Deserialize(Stream stream)
		{
			establishedTimestamp = (UInt64)BinaryMessageBeanFormat.GetInt64(stream);
			sessionKey = BinaryMessageBeanFormat.ReadUTF(stream);
		}
	}

	public class CommandMessage : Message
	{
		private MessageBeanFactory factory;
		private int commandId;

		public int CommandId {
			get { return commandId; }
			set { commandId = value; }
		}

		private MessageBean messageBean;

		public MessageBean MessageBean {
			get { return messageBean; }
			set { messageBean = value; }
		}

		public CommandMessage(MessageBean mbean, MessageBeanFactory factory) : base(MessageType.App)
		{
			commandId = mbean.commandId;
			this.messageBean = mbean;
			this.factory = factory;
		}

		public CommandMessage(MessageBeanFactory factory) : base(MessageType.App)
		{
			this.factory = factory;
			this.commandId = 0;
			this.messageBean = null;
		}

		public override void Serialize(Stream stream)
		{
			BinaryMessageBeanFormat.PutInt16(stream, (short)commandId);
			BinaryMessageBeanFormat.PutByte(stream, HEAD_TYPE);
			BinaryMessageBeanFormat.PutInt64(stream, (Int64)CurrentTimeMillis());
			factory.SerializeMessageBean(stream, messageBean);
		}

		public override void Deserialize(Stream stream)
		{
			commandId = BinaryMessageBeanFormat.GetInt16(stream);
			messageBean = factory.DeserializeMessageBean(stream, commandId);
		}
	}
}